package fr.vvins.core.common.dto;

import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.core.type.filter.TypeFilter;
import org.springframework.util.ClassUtils;

import fr.vvins.core.common.TechnicalException;
import fr.vvins.core.common.dto.annotation.Dto;
import fr.vvins.core.common.dto.annotation.DtoInfo;
import fr.vvins.core.dao.Entity;

public class DtoFactory {
	
	private final static Logger log = LoggerFactory.getLogger(DtoFactory.class);
	

	public Map<String, Class> nameToDtoMapping;
	public Map<String, Class> nameToEntityMapping;
	public Map<Class, Class> entityToDtoMapping;
	public Map<Class, Class> entityToDtoInfoMapping;
	
	
	public List<String> packagesToScan;	
	private static final String RESOURCE_PATTERN = "/**/*.class";
	private ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
	private TypeFilter dtoFilter = new AnnotationTypeFilter(Dto.class);
	private TypeFilter dtoInfoFilter = new AnnotationTypeFilter(DtoInfo.class);
	
	public DtoFactory(){
		nameToEntityMapping = Collections.synchronizedMap(new HashMap<String,Class>());
		nameToDtoMapping = Collections.synchronizedMap(new HashMap<String,Class>());
		entityToDtoMapping = Collections.synchronizedMap(new HashMap<Class,Class>());
		entityToDtoInfoMapping = Collections.synchronizedMap(new HashMap<Class,Class>());
	}
	
	public void init(){
		if(packagesToScan != null){
				for (String pkg : this.packagesToScan) {
					String pattern = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
							ClassUtils.convertClassNameToResourcePath(pkg) + RESOURCE_PATTERN;
					try {
						Resource[] resources = resourcePatternResolver.getResources(pattern);
						MetadataReaderFactory readerFactory = new CachingMetadataReaderFactory(this.resourcePatternResolver);
						for (Resource resource : resources) {
							if (resource.isReadable()) {
								MetadataReader reader = readerFactory.getMetadataReader(resource);
								String className = reader.getClassMetadata().getClassName();
								if (dtoFilter.match(reader, readerFactory)) {
									Class dtoClass = Class.forName(className);
									Dto d = (Dto) dtoClass.getAnnotation(Dto.class);
									nameToEntityMapping.put(d.name(), d.entity());
									nameToDtoMapping.put(d.name(), dtoClass);
									entityToDtoMapping.put(d.entity(),dtoClass);
								}
								if (dtoInfoFilter.match(reader, readerFactory)) {
									Class dtoClass = Class.forName(className);
									DtoInfo d = (DtoInfo) dtoClass.getAnnotation(DtoInfo.class);
									nameToEntityMapping.put("info+" + d.name(), d.entity());
									nameToDtoMapping.put("info+" + d.name(), dtoClass);
									entityToDtoInfoMapping.put(d.entity(),dtoClass);
								}
							}
						}	
					} catch (IOException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					} catch (ClassNotFoundException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
			}
		}
	}
	
	public BeanDto createDto(Class entity) throws DtoException {
		Class<BeanDto> dtoClass = null;	
		if(entityToDtoMapping.containsKey(entity)){
			dtoClass = entityToDtoMapping.get(entity);
		} else {
			throw new DtoException(TechnicalException.CONVERT, "Entity : " + entity.getSimpleName() + " n'a pas de correspondance.");
		}
		try {
			return dtoClass.newInstance();
		} catch (InstantiationException e) {
			throw new DtoException(TechnicalException.CONVERT, "Entity : " + entity.getSimpleName() + " n'a pas de correspondance.");
		} catch (IllegalAccessException e) {
			throw new DtoException(TechnicalException.CONVERT, "Entity : " + entity.getSimpleName() + " n'a pas de correspondance.");
		}
	}
	
	public BeanDtoInfo createDtoInfo(Class entity) throws DtoException {
		Class<BeanDtoInfo> dtoClass = null;	
		if(entityToDtoInfoMapping.containsKey(entity)){
			dtoClass = entityToDtoInfoMapping.get(entity);
		} else {
			throw new DtoException(TechnicalException.CONVERT, "Entity : " + entity.getSimpleName() + " n'a pas de correspondance.");
		}
		try {
			return dtoClass.newInstance();
		} catch (InstantiationException e) {
			throw new DtoException(TechnicalException.CONVERT, "Entity : " + entity.getSimpleName() + " n'a pas de correspondance.");
		} catch (IllegalAccessException e) {
			throw new DtoException(TechnicalException.CONVERT, "Entity : " + entity.getSimpleName() + " n'a pas de correspondance.");
		}
	}
	
	public BeanDto createDto(String name) throws DtoException {
		Class<BeanDto> dtoClass = null;	
		if(nameToDtoMapping.containsKey(name)){
			dtoClass = nameToDtoMapping.get(name);
		}
		try {
			return dtoClass.newInstance();
		} catch (InstantiationException e) {
			throw new DtoException(TechnicalException.CONVERT, "Dto : " + dtoClass + " n'a pas été correctement instancié.");
		} catch (IllegalAccessException e) {
			throw new DtoException(TechnicalException.CONVERT, "Dto : " + dtoClass + " n'a pas été correctement instancié.");
		}
	}
	
	public Entity createEntity(String dtoName) throws DtoException {
		Class<Entity> entityClass = null;	
		if(nameToEntityMapping.containsKey(dtoName)){
			entityClass = nameToEntityMapping.get(dtoName);
		} else {
			throw new DtoException(TechnicalException.CONVERT, "Entity pour le  dto : " + entityClass + " n'a pas été correctement instancié.");
		} 
		try {
			return entityClass.newInstance();
		} catch (InstantiationException e) {
			throw new DtoException(TechnicalException.CONVERT, "Entity pour le  dto : " + entityClass + " n'a pas été correctement instancié.");
		} catch (IllegalAccessException e) {
			throw new DtoException(TechnicalException.CONVERT, "Entity pour le  dto : " + entityClass + " n'a pas été correctement instancié.");
		}
		
	}


	public List<String> getPackagesToScan() {
		return packagesToScan;
	}

	public void setPackagesToScan(List<String> packagesToScan) {
		this.packagesToScan = packagesToScan;
	}


}
